package com.mathdroid.model;

import java.util.LinkedList;

import com.mathdroid.gui.main.DroidFrame;
import com.mathdroid.model.abilities.AbilityTemplate;
import com.mathdroid.model.buffs.BuffTemplate;
import com.mathdroid.model.encounters.EncounterTemplate;
import com.mathdroid.model.enums.EItemSlot;
import com.mathdroid.model.enums.ESpec;
import com.mathdroid.model.items.EquipedItems;
import com.mathdroid.model.items.Item;
import com.mathdroid.model.items.ItemConstraints;
import com.mathdroid.model.talents.Spec;
import com.mathdroid.model.talents.Talent;

/**
 * This utility class contains all sub models and has to be accessed in a 
 * static way. Register an observer to this class, if you want to receive 
 * events on changes. This class makes the profile independent from the
 * observers.
 */
public final class Model {
	/**
	 * The character model.
	 */
	private static Profile profile = null;
	
	/**
	 * The observers of this model.
	 */
	private static LinkedList<IModelObserver> observer = 
		new LinkedList<IModelObserver>();

	/**
	 * The window of this model.
	 */
	private static DroidFrame droidFrame;
	
	/**
	 * Hidden constructor. Not called.
	 */
	private Model() { }
	
	/**
	 * Get the profile.
	 * @return Profile.
	 */
	public static Profile getProfile() {
		return Model.profile;
	}
	
	/**
	 * Get the spec name/enumeration.
	 * @return Spec name.
	 */
	public static ESpec getESpec() {
		//TODO For now everything is a sniper, NYI, remove this proxy later
		//return Model.profile.getSpec();
		return ESpec.SNIPER;
	}
	
	/* *********************** Methods which fire events **********************/
	
	/**
	 * Set the talent tree spec.
	 * @param s Talent tree spec.
	 */
	public static void setSpec(final Spec s) {
		Model.profile.setSpecTemplate(s);
		Model.fireEvent(EModelObserverEvent.SPEC_LOADED);
	}

	/**
	 * Updates the model to the profile.
	 * @param p The profile to use.
	 */
	public static void setProfile(final Profile p) {
		Model.profile = p;
		Model.fireEvent(EModelObserverEvent.PROFILE_LOADED);
	}
	
	/**
	 * Set the buff settings.
	 * @param buffs Buff settings.
	 */
	public static void setBuffs(final BuffTemplate buffs) {
		Model.profile.setBuffTemplate(buffs);
		Model.fireEvent(EModelObserverEvent.BUFFS_LOADED);
	}
	
	/**
	 * Set the encounter settings.
	 * @param e Encounter settings.
	 */
	public static void setEncounter(final EncounterTemplate e) {
		Model.profile.setEncounter(e);
		Model.fireEvent(EModelObserverEvent.ENCOUNTER_LOADED);
	}
	
	/**
	 * The encounter model changed, force the listeners to update.
	 */
	public static void encounterChanged() {
		Model.fireEvent(EModelObserverEvent.ENCOUNTER_CHANGED);
	}
	
	/**
	 * Set the equipped items.
	 * @param t Equipped items.
	 */
	public static void setEquipedItems(final EquipedItems t) {
		Model.profile.setEquipedItems(t);
		Model.fireEvent(EModelObserverEvent.EQUIPPED_ITEMS_LOADED);
	}
	
	/**
	 * Set the ability template.
	 * @param t Ability template.
	 */
	public static void setAbilities(final AbilityTemplate t) {
		Model.profile.setAbilities(t);
		Model.fireEvent(EModelObserverEvent.ABILITIES_LOADED);
	}
	
	/**
	 * Replace the equipped item with another item.
	 * @param i The item to equip.
	 * @param slot The slot to put the item in.
	 * @return If the item was equipped.
	 */
	public static boolean equipItem(final Item i, final EItemSlot slot) {
		boolean equipped = false;
		
		if (new ItemConstraints().isEquippable(i)) {
			Model.getProfile().getEquipedItems().put(slot, i);
			Model.fireEvent(EModelObserverEvent.EQUIPPED_ITEMS_CHANGED);
			Model.refreshItemBrowserModel();
			equipped = true;
		} 
		
		return equipped;
	}
	
	/**
	 * Equip a module in a module slot of an item.
	 * @param i The module (item).
	 * @param slot The item slot to put the module in.
	 */
	public static void equipModule(final Item i, final EItemSlot slot) {
		final Item j = Model.getProfile().getEquipedItems().get(slot);
		if (j != null) {
			j.getModules().put(i.getModuleProficiency(), i);
		}
		
		Model.fireEvent(EModelObserverEvent.EQUIPPED_ITEMS_CHANGED);
		Model.refreshItemBrowserModel();
	}
	
	/**
	 * Force a refresh the item browser model.
	 */
	public static void refreshItemBrowserModel() {
		Model.profile.getItemBrowserModel().refresh();
		Model.fireEvent(EModelObserverEvent.ITEM_BROWSER_MODEL_CHANGED);
	}
	
	/**
	 * Is called if a talent changed.
	 * @param talent The changed talent.
	 */
	public static void talentChanged(final Talent talent) {
		Model.fireEvent(EModelObserverEvent.TALENT_CHANGED);
	}
	
	/**
	 * Is called if a buff or debuff was selected or unselected.
	 */
	public static void buffDebuffChanged() {
		Model.fireEvent(EModelObserverEvent.BUFF_CHANGED);
	}
	
	/**
	 * Calls the simulator for the current specialization.
	 * @param i Iterations.
	 */
	public static void simulate(final int i) {
		Model.getProfile().getClassModule().simulate(i);
		Model.fireEvent(EModelObserverEvent.SIMULATION_DONE);
	}
	
	/* *************************** Manage Observers ***************************/
	
	/**
	 * Fires a model change event.
	 * @param e Event.
	 */
	public static void fireEvent(final EModelObserverEvent e) {
		for (IModelObserver o : Model.observer) {
			o.modelChanged(e);
		}
	}
	
	/**
	 * Register an observer to this model.
	 * @param o Model observer.
	 */
	public static void registerObserver(final IModelObserver o) {
		Model.observer.add(o);
	}
	
	/**
	 * Unregister an observer from this model.
	 * @param o Observer.
	 */
	public static void unregisterObserver(final IModelObserver o) {
		Model.observer.remove(o);
	}

	/**
	 * Set the Window of this model.
	 * @param f DroidFrame.
	 */
	public static void setDroidFrame(final DroidFrame f) {
		Model.droidFrame = f;
	}
	
	/**
	 * @return Window of this model.
	 */
	public static DroidFrame getDroidFrame() {
		return Model.droidFrame;
	}
}
